#include "holicworker.h"
#include "th.h"
#include "worker.h"
#include "task.h"
#include "list.h"
#include "worker_mgr.h"
#include <assert.h>
#include <stdarg.h>
C_CODE_BEGIN

enum holicworker_cmd {
	HW_EXIT,
	HW_FINALIZE,
	HW_EXECUTE
};

struct holicworker{ 
	/*
	 * the holic worker will only have one queue (local queue)
	 * since it will use spinlock to perform the lock , it will
	 * become very efficient 
	 */
	struct list * queue;
	thread_handler th;
	enum holicworker_cmd cmd;
	bool detach;
	spinlock lk;
};

#define LOCK(hw) \
	thread_spinlock_require( &((hw)->lk), false )
#define UNLOCK(hw) \
	thread_spinlock_release( &(hw)->lk )

#define HWWORKER(w) \
	((struct holicworker*)((char*)(w) + sizeof(struct worker)))

#define WORKER(hw) \
	((struct worker*)((char*)(hw)-sizeof(struct worker)))



static void _finalize_foreach ( void* obj , void* others ) {
	struct task* t = (struct task*)(obj);
	void* ret = t->work(t->par);
	if( t->clean ) {
		t->clean(t->par,ret);
	}
}

static INLINE bool _handle_cmd( struct holicworker* hw ) {
	if( hw->cmd == HW_EXIT ) {
		return false;
	} else if( hw->cmd == HW_FINALIZE ) {
		/* The list should be OWNED by the back thread right now */
		list_foreach( hw->queue , _finalize_foreach , NULL );
		return false;
	} else {
		return true;
	}
}

static INLINE bool _idle( struct holicworker* hw ) {
	size_t loops_time =0;
	size_t inc = HOLICWORKER_YIELD_LOOPSIZE;
	while( list_get_usesize(hw->queue) == 0 ) {
		++loops_time;
		if( loops_time > HOLICWORKER_YIELD_LOOPSIZE ) {
			thread_yield();
			inc = inc/2u;
			if( inc == 0 )
				inc = 1u;
			loops_time = (size_t)(HOLICWORKER_YIELD_LOOPSIZE/inc);
		}
		/* on idle time , we can handle the command of the thread */
		if( !_handle_cmd(hw) )
			return false;
	}

	return _handle_cmd(hw);
}


static INLINE void _free( struct holicworker* hw ) {
	if( hw->detach ) {
		thread_spinlock_destroy( &(hw->lk) );
		list_destroy( hw->queue );
		/* free the memory */
		free( WORKER(hw) );
	}
}

static void* _holicworker_main( void* par ) {
	struct holicworker* hw = (struct holicworker*)(par);
	struct task* t;
	void* ret;
	while( 1 ) {
		/*
		 * Wait until there is job in the queue
		 */
		if( !_idle(hw) )
			goto __FREE;
		/*
		 * Get the job from the queue
		 */
		LOCK(hw);
		t = (struct task*)list_front( hw->queue );
		UNLOCK(hw);
		ret = t->work( t->par );
		if( t->clean ) {
			t->clean(t->par,ret);
		}
		/*
		 * Erase this job from the queue , the erase CAN NOT USE
		 * list_pop_front , since this list may be changed by another
		 * thread ,we need to LOCK THE QUEUE and then ERASE it.
		 * The list can be erased indexed by the value itself.
		 */
		LOCK(hw);
		list_pop_front( hw->queue );
		UNLOCK(hw);

	}

__FREE:
	_free(hw);
	return NULL;
}


static bool _join( struct worker* w , const struct th_join_sec* ti ) {
	struct holicworker* hw = HWWORKER(w);
	bool ret;
	assert( hw->cmd != HW_EXECUTE && hw->detach == false );
	ret = thread_join( hw->th , NULL , ti );
	hw->detach = true;
	_free(hw);
	return ret;
} 

static void* _dispatch( struct worker* w , const struct task* task ) {
	struct holicworker* hw = HWWORKER(w);
	assert( hw->cmd == HW_EXECUTE );

	LOCK(hw);
	list_push_back( hw->queue , (void*)task );
	UNLOCK(hw);
	return list_back(hw->queue);
}

static void _signal_quit( struct worker* w , bool right_now ) {
	if( right_now ) {
		HWWORKER(w)->cmd = HW_EXIT;
	} else {
		HWWORKER(w)->cmd = HW_FINALIZE;
	}
}

static size_t _get_workload( struct worker* w ) {
	return list_get_usesize( HWWORKER(w)->queue );
}

static bool _ctl( struct worker* w, const char* cmd , ... ) {
	va_list ls;
	void* tag;
	struct holicworker* hw = HWWORKER(w);
	if( strcmp(cmd,"delete") == 0 ) {
		va_start(ls,cmd);
		tag = va_arg(ls,void*);
		/*
		 * delete this worker by the void* notification 
		 */
		LOCK(hw);
		list_erase( hw->queue , tag );
		UNLOCK(hw);
		return true;
	} else {
		return false;
	}
}

static struct worker* _create( const char* name , bool detach ) {
	struct worker* w = malloc( sizeof( struct worker ) + sizeof( struct holicworker ) );
	struct holicworker* hw = HWWORKER(w);
	hw->queue = list_create( sizeof( struct task ) , HOLICWORKER_CACHE_SIZE );
	thread_spinlock_create( &(hw->lk) );
	hw->detach = detach;
	hw->cmd = HW_EXECUTE;

	if( !thread_create( &(hw->th) , name , _holicworker_main , hw , detach ) ) {
		hw->detach = true;
		_free(hw);
		return NULL;
	}
	w->dispatch = _dispatch;
	w->get_workload = _get_workload;
	w->join = _join;
	w->signal_quit = _signal_quit;
	w->ctl = _ctl;
	return w;
	
}


void _holicworker_register( struct worker_mgr* mgr ) {
	worker_mgr_register(mgr,HOLICWORKER_NAME,_create);
}

#undef LOCK
#undef UNLOCK
#undef HWWORKER
#undef WORKER


C_CODE_END